package com.g0dkar.leet.core.configuration.loaders;

import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Properties;

import com.g0dkar.leet.core.configuration.DefaultLeetConfiguration;
import com.g0dkar.leet.core.configuration.LeetConfiguration;
import com.g0dkar.leet.core.model.loaders.ModelLoaderInterface;
import com.g0dkar.leet.exception.LeetRuntimeException;
import com.g0dkar.leet.util.common.StringUtils;
import com.g0dkar.leet.util.file.FileUtils;
import com.g0dkar.leet.util.reflection.ClassFinder;
import com.g0dkar.leet.util.reflection.ClassFinder.LookupObserver;
import com.g0dkar.leet.util.reflection.ReflectionUtils;

/**
 * Creates and configures {@link LeetConfiguration} instances. It can either configure a existing instance or create a
 * new one.
 * 
 * @author g0dkar
 * 
 */
public class LeetConfigurationClassLoader implements ModelLoaderInterface<LeetConfiguration, LeetConfiguration> {
	/**
	 * This method creates and/or configures a {@link LeetConfiguration} instance.
	 * 
	 * @param configuration
	 *            The {@link LeetConfiguration} instance, or <code>null</code> if this method is to create a new
	 *            instance.
	 * @return A {@link LeetConfiguration} instance.
	 */
	public LeetConfiguration load(LeetConfiguration configuration) {
		if (configuration == null) {
			configuration = new DefaultLeetConfiguration();
		}
		
		if (StringUtils.isBlank(configuration.getName())) {
			configuration.setName("LeetApp");
		}
		if (StringUtils.isBlank(configuration.getDescription())) {
			configuration.setDescription("A Default Leet Framework Application");
		}
		
		if (configuration.getPagesCharset() == null) {
			configuration.setPagesCharset(Charset.isSupported("UTF-8") ? Charset.forName("UTF-8") : Charset.defaultCharset());
		}
		
		if (StringUtils.isBlank(configuration.getBasePackage())) {
			String basePackage = configuration.getClass().getPackage().getName();
			configuration.setBasePackage(StringUtils.isBlank(basePackage) ? "model" : basePackage);
		}
		
		if (StringUtils.isBlank(configuration.getBaseDir()) || !FileUtils.exists(configuration.getBaseDir()) || !FileUtils.getFile(configuration.getBaseDir()).isDirectory()) {
			configuration.setBaseDir(FileUtils.APPLICATION_DIRECTORY);
		}
		
		if (configuration.getClassMappings().isEmpty()) {
			configuration.addClassMappingPackage(configuration.getBasePackage());
		}
		
		if (!configuration.isInitialized()) {
			// System properties (leet.properties)
			Properties applicationProperties = new Properties();
			try {
				applicationProperties.load(new FileInputStream(FileUtils.getFile(configuration.getBaseDir() + File.separator + "leet.properties")));
			} catch (Exception e) {
				try {
					applicationProperties.load(FileUtils.getResourceAsStream(configuration.getBasePackage().replace(".", "/") + "/leet.properties"));
				} catch (Exception e1) {
					try {
						applicationProperties.load(FileUtils.getResourceAsStream("/leet.properties"));
					} catch (Exception e2) {
						// Yeah... our application really have no properties :P
					}
				}
			}
			configuration.setProperties(applicationProperties);
			
			final Collection<Class<?>> configurators = new LinkedList<Class<?>>();
			
			ClassFinder.lookup(null, null, null, new LookupObserver() {
				public void foundClass(Class<?> klass) {
					if (!klass.equals(LeetConfiguratorInterface.class) && ReflectionUtils.isImplements(klass, LeetConfiguratorInterface.class)) {
						configurators.add(klass);
					}
				}
			});
			
			if (!configurators.isEmpty()) {
				for (Class<?> configurator : configurators) {
					Object instance = ReflectionUtils.newInstance(configurator);
					
					if (LeetConfiguratorInterface.class.isAssignableFrom(configurator)) {
						try {
							((LeetConfiguratorInterface) instance).configure(configuration);
						} catch (Throwable e) {
							e.printStackTrace();
							throw new LeetRuntimeException("Exception raised by a Configurator class: %s raised %s", e, configurator.getName(), e.getClass().getName());
						}
					}
					else {
						Method method = ReflectionUtils.findMethod("configure", configurator, LeetConfiguration.class);
						
						try {
							ReflectionUtils.invokeException(method, instance, configuration);
						} catch (Throwable e) {
							e.printStackTrace();
							throw new LeetRuntimeException("Exception raised by a Configurator class: %s raised a %s", e, configurator.getName(), e.getClass().getName());
						}
					}
				}
			}
			
			configuration.setInitialized(true);
		}
		
		return configuration;
	}
}
